
#include "GarbageManager.h"

GarbageManager::GarbageManager()
    : list_(),
      totalSize_(0),
      isNeedClear_(false),
      autoGcGarbageCount_(DEF_GARBAGEMANAGER_AUTO_GC_GARBAGE_COUNT),
      autoGcGarbageMemory_(DEF_GARBAGEMANAGER_AUTO_GC_GARBAGE_MEMORY),
      autoGcInterval_(DEF_GARBAGEMANAGER_AUTO_GC_INTERVAL),
      autoGcCount_(DEF_GARBAGEMANAGER_AUTO_GC_CALL),
      autoGcIntervalVal_(0.0f),
      autoGcCountVal_(0)
{
    this->list_.reserve(DEF_GARBAGEMANAGER_RESET_COUNT);
}

void GarbageManager::Clear(bool force)
{
    if(force)
    {
        this->processMark();
        this->processRealDelete();
    }else
    {
        this->isNeedClear_ = true;
    }
}

void GarbageManager::update(float dt)
{
    this->isNeedClear_ |= this->CheckAutoGcTimer(dt);
    this->isNeedClear_ |= this->CheckAutoGcCallCount();
    if(this->isNeedClear_)
        this->Clear(true);
}

size_t GarbageManager::GetGarbageCount() const
{
    return this->list_.size();
}

size_t GarbageManager::GetGarbageMemory() const
{
    return this->totalSize_;
}

size_t GarbageManager::GetNowNewObjectCount() const
{
    return this->nowNewCount_;
}

size_t GarbageManager::GetNowNewObjectMemory() const
{
    return this->nowNewMemory_;
}

void GarbageManager::Push(GarbageObject* obj, bool markPush)
{
    if(obj && obj->isGarbage_)
    {
        this->totalSize_ += sizeof(*obj);
        this->list_.push_back(obj);

        if(markPush == false)
        {
            auto it = this->objects_.find(obj);
            if(it != this->objects_.end())
                this->objects_.erase(it);
        }

        this->isNeedClear_ |= this->CheckAutoGcGarbageCount();
        this->isNeedClear_ |= this->CheckAutoGcGarbageMemory();
    }
}

void GarbageManager::onNewObj(GarbageObject* obj)
{
    this->objects_.insert(obj);
    this->nowNewCount_ = this->nowNewCount_ + 1;
    this->nowNewMemory_ = this->nowNewMemory_ + sizeof(*obj);
}

void GarbageManager::onRemoveObj(GarbageObject* obj)
{
    this->nowNewCount_ = this->nowNewCount_ - 1;
    this->nowNewMemory_ = this->nowNewMemory_ - sizeof(*obj);
}

void GarbageManager::processMark()
{
    for(auto& it : this->rootRefTable_)
    {
        it.first->DoMark();
        it.first->gcCheckMark_ = true;
    }

    GarbageObject* obj = nullptr;
    for(auto it = this->objects_.begin(); it != this->objects_.end();)
    {
        obj = (*it);
        if(obj->isGarbage_)
            ++it;
        else
        {
            if(!obj->gcCheckMark_)
            {
                this->Release(obj);
                obj->isGarbage_ = true;
                this->Push(obj, true);
                it = this->objects_.erase(it);
            }else
            {
                if(obj->gcCheckMark_)
                    obj->gcCheckMark_ = false;
                ++it;
            }
        }
    }
}

void GarbageManager::processRealDelete()
{
    std::vector<GarbageObject*> list;
    this->list_.swap(list);
    this->list_.reserve(DEF_GARBAGEMANAGER_RESET_COUNT);
    for(auto& it : list)
        delete it;
    this->isNeedClear_ = false;
    this->totalSize_ = 0;
    this->autoGcIntervalVal_ = 0.0f;
    this->autoGcCountVal_ = 0;
}

bool GarbageManager::CheckAutoGcGarbageCount() const
{
    return autoGcGarbageCount_ > 0 && this->list_.size() >= autoGcGarbageCount_;
}

bool GarbageManager::CheckAutoGcGarbageMemory() const
{
    return autoGcGarbageMemory_ > 0 && this->totalSize_ >= autoGcGarbageMemory_;
}

bool GarbageManager::CheckAutoGcTimer(float dt)
{
    if(autoGcInterval_ <= 0)
        return false;
    this->autoGcIntervalVal_ += dt;
    return this->autoGcIntervalVal_ >= this->autoGcInterval_;
}

bool GarbageManager::CheckAutoGcCallCount()
{
    if(autoGcCount_ <= 0)
        return false;
    this->autoGcCountVal_ += 1;
    return this->autoGcCountVal_ >= this->autoGcCount_;
}

void GarbageManager::Release(GarbageObject* target)
{
	auto it = this->rootRefTable_.find(target);
	if(it != this->rootRefTable_.end())
	{
		it->second = it->second - 1;
		if(it->second <= 0)
			this->rootRefTable_.erase(it);
	}
}

void GarbageManager::Retain(GarbageObject* target)
{
	auto it = this->rootRefTable_.find(target);
	if(it != this->rootRefTable_.end())
		it->second = it->second + 1;
	else
		this->rootRefTable_[target] = 1;
}